<?php
declare (strict_types=1);

namespace app\course\logic\base;

use app\admin\logic\system\Config as SystemConfig;
use app\common\exception\LogicException;
use app\common\logic\category\Category;
use app\common\model\{Attach};
use app\course\logic\classes\{Classes, ClassesLink};
use app\course\logic\coupon\Coupon;
use app\course\logic\courseware\admin\Courseware;
use app\course\logic\live\LiveSection;
use app\course\logic\record\LearnRecord;
use app\course\model\base\Course as BaseCourse;
use app\course\model\base\CourseWareRecord;
use app\course\model\live\CourseSectionLive;
use app\course\model\payment\PaymentOrderItem as POI;
use app\course\model\record\{LearnCourseRate};
use app\course\model\video\CourseSectionVideo;
use app\exam\logic\admin\Popup;
use app\exam\logic\Topic as TopicLogic;
use app\exam\model\PaperAnswer;
use app\promotion\logic\{Promotion, PromotionBargain, PromotionTuan};
use app\school\logic\school\School;
use app\user\logic\teacher\Teacher;
use app\user\logic\user\Collection;
use app\user\logic\user\FaceVerify;
use app\user\model\user\Favorite;
use ArrayObject;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\db\Query;
use think\facade\Request;
use think\Paginator;

/**
 * 课程总接口-不区分课程类型
 * @method orderRaw(string $string)
 * @method orderFavorite()
 * @method join(string $string, string $string1)
 * @method whereId($courseId)
 * @method searchTitle(mixed $param)
 */
class Course extends BaseCourse
{

    /**
     * 课程列表
     * Author   亓官雨树<lucky.max@foxmail.com>
     * Date 2020-03-16
     *
     * @throws DbException
     */
    public function getCourseList(): Paginator
    {
        // 搜索功能
        // 示例 Request 变量获取 亦可用input助手函数
        // Request::has('course_type', 'get', true) && $map[] = ['course_type', '=', Request::get('course_type/s')];
        $map = [];
        // 通用搜索
        if (input('?get.title')) {
            $title = trim(input('get.title/s'), ' ');
            if ($title) {
                $map[] = ['title', 'like', '%' . $title . '%'];
                app(SearchLog::class)->addLog($title);
            }
        }
        // 课程机构
        if (1 == request()->mhm_id) {
            $raw      = 'category IS NOT NULL AND category <> ""';
            $cate_str = 'category';
        } else {
            $raw      = 'mhm_id=' . request()->mhm_id;
            $cate_str = 'mhm_category';
        }
        $raw .= ' AND (term_time < 10000 OR term_time > ' . time() . ')';
        // 课程类型
        input('?get.course_type') && $map[] = ['course_type', '=', input('get.course_type/d')];
        // 更新状态
        input('?get.update_status') && $map[] = ['update_status', '=', input('get.update_status/d')];
        // 课程分类
        if (input('?get.category') && input('get.category/d')) {
            $map[] = [$cate_str, 'LIKE', '%,' . input('get.category/d') . ',%'];
        }
        // 价格区间
        if (input('?get.price_min') && input('?get.price_max')) {
            $map[] = ['price', 'BETWEEN', [input('get.price_min/d'), input('get.price_max/d')]];
        } else if (input('?get.price_min')) {
            $map[] = ['price', '>=', input('get.price_min/d')];
        } else if (input('?get.price_max')) {
            $map[] = ['price', '<=', input('get.price_max/d')];
        }
        // 直播状态
        if (input('?get.live')) {
            $live = strtolower(input('get.live/s')) ?: 'on';
            if ('soon' == $live) {
                // 今日零点 - 明日零点
                $today_start = strtotime('today');
                $today_end   = strtotime('tomorrow');
                $ids         = app(CourseSection::class)->where('start_time', 'BETWEEN', [$today_start, $today_end])->group('course_id')->column('course_id');
            } else {
                $ids = app(LiveSection::class)->whereNull('close_time')->group('course_id')->column('course_id');
            }
            $map[] = ['id', 'IN', $ids];
        }
        // 查询范围
        $scope = ['status', 'listingTime', 'solo', 'expire'];
        input('?get.order') && $scope[] = 'order' . ucfirst(input('get.order/s'));
        input('?get.price_order') && $scope[] = 'orderPrice' . ucfirst(input('get.price_order/s'));

        if (input('?get.param') && input('get.param/s') == 'audition' && input('?get.course_type') && input('get.course_type/d') == 4) {
            $ids   = app(ClassesLink::class)->alias('c')->join('course_section s', 's.course_id=c.course_id')->where('audition', '>', 0)->group('c.classes_id')->column('c.classes_id');
            $map[] = ['id', 'IN', $ids];
        } else if (input('?get.param')) {
            $scope[] = 'param' . ucfirst(input('get.param/s'));
        }
        // 查询字段
        $field = 'id,title,course_type,price,cover,sale_count,sale_count_market';
        // 活动类型名称
        $promotion_type_text = Promotion::getTypeText();
        // 数据结果
        return $this->scope($scope)->where($map)->whereRaw($raw)->field($field)
            // ->buildSql();
            ->with(['cover_url'])->order(['update_time' => 'DESC'])
            // ->visible(['id'])
            ->append(['course_type_text', 'user_price'])->paginate(static::getListRows())->each(function ($item) use ($promotion_type_text) {
                // 当前用户是否购买
                $item->is_buy = app(CourseStudent::class)->isBuy($item->id);
                // 获取活动类型，无活动返回0
                $item->promotion_type      = app(Promotion::class)->getProductCurrentPromotionType($item->course_type, $item->id);
                $item->promotion_type_text = $promotion_type_text[$item->promotion_type] ?? '';
                // 直播处理
                $item->start_time = 0;
                if ($item->course_type == 2) {
                    $item->start_time = app(CourseSection::class)->where('course_id', '=', $item->id)->where('course_type', '=', 2)->where('start_time', '>', strtotime('today'))->where('end_time', '>', time())->order('start_time')->value('start_time') ?? 0;
                }
            });
    }

    /**
     * 课程详情
     * Author   亓官雨树<lucky.max@foxmail.com>
     * Date 2020-03-16
     */
    public function getCourseDetails($id)
    {
        // $field = '*';
        $field = 'id,course_type,title,cover,scribing_price,price,vip_price,section_level,mhm_id,teacher_id,info,term_time,course_live_type,update_status,status,solo,open_comment,score_average,score_nums,score_sums,view_count,sale_count,exam_condition_rate,sale_count_market,look_need_login,is_marquee,onhook_type';
        $data  = $this->where('id', $id)->field($field)->with(['cover_url', 'coupon_count', 'exam'])->withCount(['section_count' => 'section_count'])->findOrEmpty()->append(['score_star', 'course_type_text', 'user_price']);
        //
        if ($data->isEmpty()) throw new LogicException('课程不存在');
        // 购买情况
        $data->is_buy = app(CourseStudent::class)->isBuy($data->id);
        // 禁用课程未购买提示
        if (!$data->status && !$data->is_buy) throw new LogicException('课程已禁用');
        // 购买过课时
        $bought_sec = app(POI::class)->mid()->where('product_id', $id)->where('section_id', '<>', 0)->whereRaw('expire_time is null OR expire_time > ' . time())->count();
        // 非单卖课程只能购买才能进
        if (!$data->solo && !$data->is_buy && !$bought_sec) throw new LogicException('课程已禁用.');

        return $this->integrateData($data);
    }

    /**
     * 整合数据
     * Author   亓官雨树<lucky.max@foxmail.com>
     * Date 2020-03-16
     */
    public function integrateData($data)
    {
        // 班级课处理
        if ($data->course_type == 4) {
            $data->section_count = app(Classes::class)->getClassesSectionCount($data->id);
            $courseIds           = app(ClassesLink::class)->getCourseIds($data->id);
        } else {
            $courseIds = $data->id;
        }
        $data->course_ids = $courseIds;
        // 当前用户此课程已完成的课时
        $finished_num       = app(LearnRecord::class)->finished($courseIds)->count();
        $data->finished_num = $finished_num;
        // 继续学习
        $data = $this->parseRecentLearn($data);

        if ($data->is_buy) {
            // 购买后有效期
            $data->term_rest = app(CourseStudent::class)->getTermRest($data->id);
        } else {
            $data->term_rest = ($data['term_time'] && strlen($data['term_time'] . '') > 4) ? max(0, $data['term_time'] - time()) : -1;
        }
        // 收藏情况
        $data->collected = app(Collection::class)->collected('course', $data->id);
        // 简介
        $data->info_url  = Request::domain() . Request::baseUrl() . '/info';
        $data->info_text = $data->info;
        if (in_array(request()->device_type, ['ios', 'android'])) $data->info = Request::domain() . Request::baseUrl() . '/info';
        // 推荐卡券
        try {
            $data->recommend_coupon = app(Coupon::class)->getCourseRecommendCoupon($data->id);
        } catch (DataNotFoundException|ModelNotFoundException|DbException $e) {
        }
        // 讲师信息
        $data->teachers = app(Teacher::class)->getCourseTeacher($data);
        // 机构信息
        $data->mhm_info = $data->mhm_id == 1 ? null : app(School::class)->getSchoolInfo($data->mhm_id);
        // 机构独立域名
        $data->mhm_sdn = app(School::class)->getSdn($data->mhm_id);
        // 班级学员
        $data->student = app(CourseStudent::class)->getCourseStudents($data->id);
        // 目录下试看状态
        $data->audition_stat = $this->auditionStat($data->id, 0, $data->course_type == 4);
        // 咨询
        $site_info            = SystemConfig::get('site.info', [], request()->mhm_id);
        $data->online_consult = $site_info['online_consult'] ?? '';
        // 分享强制登录配置
        $share_config            = SystemConfig::get('share.config', ['course' => 0, 'topic' => 0]);
        $data->share_force_login = $share_config['course'] ?? 0;
        // 活动
        $promotion       = app(Promotion::class)->getProductCurrentPromotion($data->course_type, $data->id);
        $data->promotion = $promotion->isEmpty() ? new ArrayObject() : $promotion;

        if (!$promotion->isEmpty()) {
            // 当前用户砍价团购情况
            // 存在砍价
            if ($promotion->type == 3) {
                $done               = app(PromotionBargain::class)->promotionId($promotion->id)->sponsor()->value('done');
                $data->bargain_done = $done ? 1 : 0;
            } else
                // 存在团购
                if ($promotion->type == 4) {
                    $tuan = app(PromotionTuan::class)->getUserTuanBase($promotion->id);

                    $data->have_pintuan = $tuan->isEmpty() ? 0 : 1;
                    $data->pintuan_data = $tuan->isEmpty() ? new ArrayObject() : $tuan;
                }
        }
        // 课程学习进度
        $rate             = app(LearnCourseRate::class)->mid()->course($data->id)->value('rate');
        $rate             = $rate ?: 0;
        $data->learn_rate = $rate;
        // 可考状态
        $data->can_exam = ($data->exam_condition_rate <= $rate && $data->is_buy) ? 1 : 0;
        // 试卷可考次数
        foreach ($data->exam as $paper) {
            $answer_times        = app(PaperAnswer::class)->paperId($paper->paper_id)->current()->courseId($data->id)->max('answer_times');//->buildSql();//
            $paper->answer_times = $answer_times;
            $paper->can_exam     = $paper->exam_number ? ($answer_times < $paper->exam_number ? 1 : 0) : 1;
        }
        // 直播课程正在直播课时信息
        if ($data->course_type == 2) {
            $time  = time();
            $secId = app(CourseSection::class)->whereCourseId($data->id)->whereIsLast(1)->where('start_time', '<=', $time)->where('end_time', '>=', $time)->order(['best_sort' => 'ASC', 'id' => 'ASC'])->value('id');
            $live  = app(CourseSectionLive::class)->where('room_no', $secId)->findOrEmpty();
            if (!$live->isEmpty()) {
                $live->data_type = 1;
                // 当前用户身份【当课讲师、其他】
                $live->identify = 'talker'; // 其他
                // 课程属于讲师
                $teacher_id = $this->getCourseTeacherUid($live->course_id);
                if ($teacher_id == request()->mid) $live->identify = 'presenter';// 当课讲师
            }
            $data->section_live = $live->isEmpty() ? new ArrayObject() : $live;
        }

        return $data;
    }

    // 指定课程、目录是否有试看
    public function auditionStat($courseId = 0, $pid = 0, $classes = false): int
    {

        $ids = $classes ? app(ClassesLink::class)->getCourseIds($courseId) : [$courseId];

        $data = app(CourseSection::class)->whereIn('course_id', $ids)->where('audition', '<>', 0)->field('id,pid,audition')->select();

        if ($data->isEmpty()) return 0;

        $pids = array_values(array_unique(array_column($data->toArray(), 'pid')));

        return $this->checkAudition($pids, $pid);
    }

    // 检查试看
    public function checkAudition($pids = [], $pid = 0): int
    {

        if (in_array($pid, $pids)) {
            return 1;
        } else if ($pids != [0]) {
            $pids = app(CourseSection::class)->where('id', 'IN', $pids)->column('pid');
            return $this->checkAudition(array_unique($pids), $pid);
        }

        return 0;
    }

    // 最近再学
    public function parseRecentLearn($data)
    {
        /** @var LearnRecord $record_mod */
        $record_mod        = app(LearnRecord::class);
        $data->is_learning = $record_mod->isLearning($data->course_ids);
        $recent_learn      = new ArrayObject();

        if ($data->is_learning) {
            $recent_learn_sec = $record_mod->recentLearnSection($data->course_ids);
            if (!$recent_learn_sec->isEmpty()) {
                $recent_learn['course_id']    = $recent_learn_sec->course_id;
                $recent_learn['section_id']   = $recent_learn_sec->id;
                $recent_learn['current_time'] = $recent_learn_sec->current_time;
                $recent_learn['section_data'] = $recent_learn_sec->section_data;
            }
        } else {
            $courseId = $data->id;
            // 班级课处理
            if ($data->course_type == 4) {
                $courseId = app(ClassesLink::class)->alias('link')->join('course', 'course.id=link.course_id AND course.course_type=1')->where('classes_id', $data->id)->value('course_id');
            }

            $info = app(CourseSection::class)->firstSectionHour($courseId);
            if (!$info->isEmpty()) {
                $recent_learn['course_id']    = $info->course_id;
                $recent_learn['section_id']   = $info->id;
                $recent_learn['current_time'] = 0;
                $recent_learn['section_data'] = $info->section_data;
            }
        }

        if (isset($recent_learn['section_id'])) {
            // 人脸认证数据
            /** @var FaceVerify $faceVerify */
            $faceVerify                = app(FaceVerify::class);
            $recent_learn['face_data'] = $faceVerify->getFaceData('course_section', 2, intval($recent_learn['section_id']), request()->mid);
        }

        $data->recent_learn = $recent_learn;

        return $data;
    }

    /**
     * 详情页推荐课程
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2022-01-27
     *
     * @param  [type] $id [description]
     * @return mixed
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function getCourseSplendid($id)
    {
        $mod = $this->getSplendidMod($id);
        // 课程
        $course = $this->findOrEmpty($id);
        // 机构站点只推荐机构内课程
        $platform = request()->mhm_id == 1;
        $category = $platform ? $course->fullcategorypath : (empty($course->mhm_fullcategorypath) ? $course->fullcategorypath : $course->mhm_fullcategorypath);

        if (count($category)) {
            $cates = [];
            $raw   = '';
            foreach ($category as $cate) {
                if (!in_array(current($cate), $cates)) {
                    $raw .= "category LIKE '%," . current($cate) . ",%' OR ";
                    // 机构站点查询添加机构分类
                    if (!$platform) $raw .= " mhm_category LIKE '%," . current($cate) . ",%' OR ";
                    // 存储已知分类
                    $cates[] = current($cate);
                }
            }
            $mod = $mod->whereRaw('(' . rtrim($raw, 'OR ') . ')');
        }

        $data = $mod->with(['cover_url'])->order(['is_recommend' => 'DESC', 'best_sort' => 'asc', 'update_time' => 'DESC'])->limit(4)
            // ->buildSql()
            ->select()->append(['course_type_text', 'user_price'])->toArray();

        if (count($data) < 4) {
            $model = $this->getSplendidMod($id);
            $rest  = $model->where('is_recommend', 1)->where('mhm_id', request()->mhm_id)->with(['cover_url'])->order(['best_sort' => 'asc', 'update_time' => 'DESC'])->limit(4 - count($data))
                // ->buildSql()
                ->select()->append(['course_type_text', 'user_price'])->toArray();
            return array_merge($data, $rest);
        }
        return $data;
    }

    /**
     * 推荐课程model
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2022-01-27
     *
     * @param  [type] $id [description]
     */
    private function getSplendidMod($id): Query
    {
        $scope = ['status', 'listingTime', 'solo', 'expire'];
        $mod   = $this
            ->field('id,title,cover,course_type,price,vip_price,category,sale_count,sale_count_market')
            ->scope($scope)
            ->where('id', '<>', $id);

        if (request()->mid) {
            $notSql = app(CourseStudent::class)->whereUserId(request()->mid)->column('course_id');
            $mod    = $mod->whereNotIn('id', $notSql);
        }
        if (request()->mhm_id != 1) $mod = $mod->where('mhm_id', request()->mhm_id);
        return $mod;
    }

    // 课程简介
    public function getCourseInfo($id)
    {
        $data = $this->where('id', $id)->field('info')->findOrEmpty();
        return $data->info;
    }

    /**
     * 获取指定章节
     * Author   亓官雨树<lucky.max@foxmail.com>
     * Date 2020-04-08
     */
    public function getCourseAppointSections($id, $pid, int $classId): array
    {
        $course = $this->findOrEmpty($id);

        if ($course->isEmpty()) throw new LogicException('课程不存在');

        $secModel = new CourseSection();
        try {
            $section_info = $secModel->getCourseAppointSections($id, $pid, $classId);
        } catch (DbException $e) {
            $section_info = [];
        }

        //
        $course_buy_status = app(CourseStudent::class)->isBuy($id);

        return [
            'section_level'     => $course->section_level,
            'audition_stat'     => $this->auditionStat($id, $pid),
            'course_buy_status' => $course_buy_status,
            'section_info'      => $section_info,
        ];
    }

    /**
     * 获取课时fileurl
     * Author   亓官雨树<lucky.max@foxmail.com>
     * Date 2020-08-20
     */
    public function getCourseSectionFileurl($courseId, $section_id, $class_id): array
    {
        // 课时信息
        $data = app(CourseSection::class)->with(['course_lock'])->findOrFail($section_id);
        //
        if ($courseId != $data->course_id) throw new LogicException('课程与课时不匹配');
        //
        if ($data->course_type != 1) throw new LogicException('非点播课程不可用');
        // 课时data
        $dataInfo = app(CourseSectionVideo::class)->findOrFail($data->course_data_id);
        // 课时附件地址
        if ($dataInfo->data_type == 3) {
            $fileurl = in_array(request()->device_type, ['h5', 'miniprogram']) ? $dataInfo->getAttr('data_txt') : $dataInfo->getAttr('data_txt_url');
        } else {
            // 附件数据
            $attach = app(Attach::class)->findOrEmpty($dataInfo->attach_id);

            if ($attach->isEmpty()) throw new LogicException('数据错误');
            $fileurl = $attach->stock == 'bokecc' ? [] : $attach->fileurl;
        }
        $return['course_lock']    = $data->course_lock;
        $return['url_type']       = is_null($fileurl) ? 0 : (is_array($fileurl) ? 1 : 2);
        $return['url_type_text']  = is_null($fileurl) ? 'null' : (is_array($fileurl) ? 'array' : 'string');
        $return['fileurl_string'] = '';
        $return['fileurl_array']  = [];

        if ($return['url_type'] == 1) {
            $return['fileurl_array'] = $fileurl;
        } else if ($return['url_type'] == 2) {
            $return['fileurl_string'] = $fileurl;
            $return['fileurl_is_url'] = !!strpos($fileurl, '/course/section_data/');
            if ($dataInfo->data_type == 3 && $return['fileurl_is_url']) {
                $return['fileurl_string_text'] = $dataInfo->getAttr('data_txt');
            }
        }
        $ids  = [intval($section_id)];
        $posi = $this->getCurrSecPosition($ids, $data->pid);
        $curr = ['course_id' => $courseId, 'course_type' => $data->course_type, 'position' => $posi];
        // 购买情况
        $course_buy = app(CourseStudent::class)->isBuy($courseId);
        if ($course_buy) {
            $nextPosi = $this->getNextSecPosition($courseId, $section_id, $class_id);
            $next     = ['course_id' => $courseId, 'course_type' => $data->course_type, 'position' => $nextPosi];
            if (count($nextPosi)) {
                $ids         = $next['position'];
                $sec_id      = end($ids);
                $next['id']  = $sec_id;
                $next['pid'] = $ids[count($ids) - 2] ?? 0;
                //
                $rate                 = app(LearnRecord::class)->field('current_time')->where('section_id', $sec_id)->scope('mid')->findOrEmpty();
                $next['section_rate'] = $rate->isEmpty() ? ['current_time' => 0] : $rate;
                //
                $section_data = app(CourseSectionVideo::class)->alias('d')->join('course_section s', 's.course_data_id=d.id')->field('d.data_type,d.attach_id')->where('s.id', $sec_id)->findOrEmpty()->append();
                if ($section_data->isEmpty()) {
                    $next['section_data'] = new ArrayObject();
                } else {
                    if ($attachId = $section_data->attach_id) $section_data->stock = app(Attach::class)->where('id', $attachId)->value('stock') ?: 'local';
                    $next['section_data'] = $section_data;
                }
            }
        } else {
            $next = new ArrayObject();
        }
        $return['curr_position'] = $curr;
        $return['next_position'] = $next;
        // 人脸认证数据
        /** @var FaceVerify $faceVerify */
        // $faceVerify          = app(FaceVerify::class);
        // $return['face_data'] = $faceVerify->getFaceData('course_section', 2, intval($section_id), request()->mid);
        // 跑马灯
        // $mhmId             = app(Course::class)->where('id', $courseId)->value('mhm_id');
        // $return['marquee'] = SystemConfig::get('course.video.marquee', [], $mhmId);
        // 判断是否为班级课下的课程
        $courseIds   = app(ClassesLink::class)->getCourseIds($class_id);
        $newCourseId = in_array($courseId, $courseIds) ? $class_id : $courseId;
        // 跑马灯配置
        $course = app(Course::class)->where('id', $newCourseId)->field('mhm_id,is_marquee,onhook_type')->findOrEmpty();
        if ($course->is_marquee == 1) {
            // 跑马灯
            $mhmId   = request()->mhm_id;//$course->mhm_id;
            $marquee = SystemConfig::get('course.video.marquee', [], $mhmId, true);
            if ($marquee) {
                $marquee['font_color'] = substr($marquee['font_color'], 1);
            }
        }
        $return['marquee'] = $marquee ?? new ArrayObject();
        // 防挂机配置
        if ($course->onhook_type == 1) {
            // 弹窗
            $system                        = SystemConfig::get('system.onhook', ['alert_max_number' => 0, 'alert_time_interval' => 0, 'alert_content' => ''], request()->mhm_id, true);
            $onhook['alert_max_number']    = $system['alert_max_number'] ?? 0;
            $onhook['alert_time_interval'] = $system['alert_time_interval'] ?? 0;
            $onhook['alert_content']       = $system['alert_content'] ?? '';
        } else if ($course->onhook_type == 2) {
            // 弹题
            $question = $this->getQuestion(request()->mhm_id);
        } else if ($course->onhook_type == 3) {
            // 人脸
            /** @var FaceVerify $faceVerify */
            $faceVerify = app(FaceVerify::class);
            $face_data  = $faceVerify->getFaceData('course_section', 2, intval($section_id), request()->mid, request()->mhm_id);
        }
        $return['face_data']   = $face_data ?? new ArrayObject();
        $return['onhook']      = $onhook ?? new ArrayObject();
        $return['onhook_exam'] = $question ?? new ArrayObject();

        return $return;
    }

    /**
     * 获取当前课时定位
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-10-12
     */
    public function getCurrSecPosition($ids, $pid)
    {
        if (!$pid) return $ids;

        $data = app(CourseSection::class)->whereId($pid)->field('id,pid')->findOrEmpty();

        if (!$data->isEmpty()) {
            array_unshift($ids, $data->id);
            if ($data->pid) return $this->getCurrSecPosition($ids, $data->pid);
        }

        return $ids;
    }

    /**
     * 获取续播课时定位
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-10-12
     */
    public function getNextSecPosition($courseId, $id, $classId = 0): array
    {
        $pid     = app(CourseSection::class)->whereId($id)->value('pid');
        $nextSec = $this->getNextSecId($id, $courseId, $pid, $classId);
        if ($nextSec) {
            return $this->getCurrSecPosition([$nextSec[1]], $nextSec[0]);
        }

        if (!$classId) return [];

        $course = app(ClassesLink::class)->getClassesNextCourseByCurr($classId, $courseId);
        if ($course->isEmpty()) return [];

        $id  = app(CourseSection::class)->getCourseCurrSecLast($course->course_id);
        $sec = app(CourseSection::class)->CoLaSeSa($course->course_id)->field('id,pid')->findOrEmpty($id);

        if ($sec->isEmpty()) return [];

        $ids2 = [$sec->id];
        return $this->getCurrSecPosition($ids2, $sec->pid);
    }

    public function getNextSecId($id, $courseId, $pid, $next = false)
    {
        // 当前章节课时ID集
        $secIds = app(CourseSection::class)->CoLaSeSa($courseId)->wherePid($pid)->order(['best_sort', 'id'])->column('id');
        if (count($secIds) && $id != end($secIds)) {
            if ($next) {
                return [$pid, $secIds[0]];
            } else {
                $needle = array_search($id, $secIds);
                return [$pid, $secIds[$needle + 1]];
            }
        } else {
            $pid = app(CourseSection::class)->CoLaSeSa($courseId)->where('pid', '>', $pid)->order(['pid'])->value('pid');
            if ($pid) {
                return $this->getNextSecId($id, $courseId, $pid, true);
            } else {
                return false;
            }
        }
    }

    /**
     * 获取续播课时
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-10-12
     */
    public function getNextSec($id, $pid)
    {
        $data = app(CourseSection::class)->wherePid($pid)->field('id,pid,is_last')->where('id', '>', $id)->findOrEmpty();

        if (!$data->isEmpty()) {
            if ($data->is_last) return $data;
            return $this->getSecLast($data);
        } else {
            $prev_pid = app(CourseSection::class)->whereId($pid)->value('pid');
            return $this->getNextSec($pid, $prev_pid);
        }
    }

    /**
     * 获取章节第一个课时
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-10-12
     */
    public function getSecLast($sec)
    {
        $data = app(CourseSection::class)->wherePid($sec->id)->field('id,pid,is_last')->findOrEmpty();
        if ($data->isEmpty()) return $sec;
        if (!$data->isEmpty() && $data->is_last) return $data;

        return $this->getSecLast($data);
    }

    /**
     * 订单页获取的产品信息
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date：   2020-05-08
     */
    public function getProductInfo($data)
    {
        $field             = 'id,title,mhm_id,cover,course_type,price,scribing_price,vip_price,section_level';
        $info              = $this->field($field)->with(['cover_url', 'school'])->findOrEmpty($data->product_id);
        $info->payment     = $data->payment;
        $info->type_id     = $info->course_type;
        $info->type_text   = $info->course_type_text;
        $info->expire_time = $data->expire_time;
        $info->expire_rest = $data->expire_time ? ceil(max(0, $data->expire_time - time()) / 86400) : -1;

        return $info;
    }

    /**
     * 讲师热门课程
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date：   2020-06-07
     */
    public function teacherHotCourse($id, $limit = 6)
    {
        return $this->where('teacher_id', $id)->scope('status,listingTime,solo,expire')->field('id,title,course_type,cover,price,vip_price,sale_count,sale_count_market')->with(['cover_url'])->order(['sale_count' => 'DESC', 'best_sort' => 'DESC'])->limit($limit)->select()->append(['course_type_text', 'user_price']);
    }

    /**
     * 用户意向课程
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date：   2020-06-15
     */
    public function getFavoriteTitle()
    {
        $mid     = request()->mid;
        $mhm_id  = request()->mhm_id;
        $cate_id = app(Favorite::class)->where('user_id', $mid)->value('category_id');
        // 设置默认
        !$cate_id && $cate_id = app(Category::class)->whereMhmId($mhm_id)->whereCategoryType(0)->order('id')->value('id');
        // 查询数据
        if (!$cate_id) return '';

        $title = cache('favorite_title_' . $mhm_id . '_' . $mid);
        if (!$title) {
            $title = app(Category::class)->where('id', $cate_id)->value('title');
            cache('favorite_title_' . $mhm_id . '_' . $mid, $title, 3600 * 2);
        }
        // 返回数据
        return $title;
    }

    /**
     * 用户意向课程
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date：   2020-06-15
     */
    public function getFavoriteCourse($limit = 5)
    {
        $mid     = request()->mid;
        $mhm_id  = request()->mhm_id;
        $cate_id = app(Favorite::class)->where('user_id', $mid)->value('category_id');
        !$cate_id && $cate_id = app(Category::class)->whereMhmId($mhm_id)->whereCategoryType(0)->order('id')->value('id');
        // 查询数据
        if (!$cate_id) return [];

        // $course = cache('favorite_course_' . $mhm_id . '_' . $mid);
        // if (!$course) {
        $cate = $mhm_id == 1 ? 'category' : 'mhm_category';
        //     cache('favorite_course_' . $mhm_id . '_' . $mid, $course, 3600 * 2);
        // }
        // 返回数据
        return $this->where($cate, 'LIKE', '%,' . $cate_id . ',%')->where('mhm_id', $mhm_id)->scope('status,listingTime,solo,expire')->orderFavorite()->field('id,course_type,title,price,sale_count,sale_count_market,cover,status')->with(['cover_url'])->limit($limit)->select()->append(['course_type_text', 'user_price'])->each(function ($i) {
            $i->is_buy = app(CourseStudent::class)->isBuy($i->id);
        })->toArray();
    }

    // 首页限时打折课程
    public function getFlashSaleCourse()
    {
        $map = [
            'p.status' => 1,
            'c.solo'   => 1,
            'c.status' => 1,
        ];
        if (request()->mhm_id != 1) $map['c.mhm_id'] = request()->mhm_id;
        return app(Promotion::class)->alias('p')
            ->join('course c', 'c.id=p.product_id')
            ->field('p.*')
            ->whereType(1)->startTime()->endTime()->where($map)->whereNull('c.delete_time')
            ->with(['product_info'])
            ->select()
            ->append(['start_countdown', 'end_countdown']);
    }

    /**
     * 首页推荐课程
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date：   2020-06-17
     */
    public function getRecommend(): array
    {
        $map   = [];
        $map[] = ['is_recommend', '=', 1];
        $raw   = (1 == request()->mhm_id) ? 'category IS NOT NULL AND category <> ""' : 'mhm_id=' . request()->mhm_id;
        $is_pc = request()->is_pc;
        $limit = $is_pc ? 8 : 6;

        return $this->where($map)->whereRaw($raw)->with(['coverUrl'])->field('id,title,cover,sale_count,sale_count_market,price,course_type')->scope('status,listingTime,solo,expire')->limit($limit)->order(['best_sort', 'update_time' => 'DESC'])->select()->append(['course_type_text', 'user_price'])->toArray();
    }

    /**
     * 首页热销课程
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date：   2020-06-18
     */
    public function getRecommendWellSale(): array
    {
        $last_monday = strtotime('last Monday');
        $last_month  = strtotime(date('Y-m-01', strtotime(date('Y-m-d'))));
        $limit       = request()->device_type == 'pc' ? 8 : 5;
        // 周榜
        $week_data = $this->getTimeCourseWithCount($last_monday, $limit);
        // 月榜
        $month_data = $this->getTimeCourseWithCount($last_month, $limit);
        return [
            ['key' => 'week', 'list' => $week_data],
            ['key' => 'month', 'list' => $month_data],
        ];
    }

    /**
     * 获取指定时间之后课程及其销量
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date：   2020-06-18
     */
    public function getTimeCourseWithCount($time, $limit): array
    {
        $info = app(POI::class)->recommendWellSale($time, $limit)->select()->toArray();
        $data = [];
        if (count($info)) {
            // 将$info数组转一维，id做键，count做值
            $id_count = $this->keyValueArr($info, 'id', 'count');
            $ids_str  = implode(',', array_keys($id_count));
            // halt($ids_str);
            $map   = [];
            $map[] = ['id', 'IN', $ids_str];
            $raw   = (1 == request()->mhm_id) ? 'category IS NOT NULL AND category <> ""' : 'mhm_id=' . request()->mhm_id;
            $data  = $this->field('id,title,course_type,cover,price')->where($map)->whereRaw($raw)->withCount(['order_item' => 'order_count'])
                ->scope('status,listingTime,solo,expire')->orderRaw("FIND_IN_SET(id,'$ids_str')")
                // ->buildSql();
                ->select()->append(['course_type_text', 'cover_url', 'user_price'])->each(function ($i) use ($id_count) {
                    $i->sale_count = $id_count[$i->id];
                })->toArray();
        }
        return $data;
    }

    /**
     * 将二维数组转一维，其中一字段做键，另一字段做值
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date：   2020-06-18
     */
    public function keyValueArr($arr, $key, $value)
    {
        return array_reduce($arr, function (&$new, $i) use ($key, $value) {
            $new[$i[$key]] = $i[$value];
            return $new;
        });
    }

    /**
     * 首页推荐分类课程
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date：   2020-06-18
     */
    public function getCategoryCourse(): array
    {
        // 推荐分类
        $cate  = app(Category::class)->where('pid', 0)->where('mhm_id', '=', request()->mhm_id)->field('id,title')->order('best_sort', 'ASC')->limit(3)->select();
        $list  = [];
        $limit = request()->device_type == 'pc' ? 8 : 6;

        foreach ($cate as $c) {
            $data['category_id']    = $c->id;
            $data['category_title'] = $c->title;

            $course = cache('category_course_' . request()->mhm_id . '_' . $c->id);
            if (!$course) {
                $course = $this->getRecommendCourseByCate($c->id, $limit);
                cache('category_course_' . request()->mhm_id . '_' . $c->id, $course, 3600 * 2);
            }

            $data['data'] = $course;

            $list[] = $data;
        }
        return $list;
    }

    /**
     * getRecommendCourseByCate
     * Author: 亓官雨树 <lucky.max@foxmail.com>
     * Date: 23/07/14
     *
     * @param $cate
     * @param $limit
     * @return mixed
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function getRecommendCourseByCate($cate, $limit)
    {
        $course = $this->field('id,title,price,sale_count,sale_count_market,cover,course_type')->with(['cover_url'])->scope('status,listingTime,solo,expire')->where('category', 'LIKE', '%,' . $cate . ',%')->where('is_recommend', 1)
            // ->order('best_sort', 'ASC')
            // ->order([
            //     'sale_count'    => 'DESC',
            //     'score_average' => 'DESC',
            //     'score_nums'    => 'DESC',
            // ])
            ->limit($limit)->select()->append(['course_type_text', 'user_price']);

        $diff = $limit - count($course);
        if ($diff > 0) {
            $_course = $this->field('id,title,price,sale_count,sale_count_market,cover,course_type')->with(['cover_url'])->scope('status,listingTime,solo,expire')->where('category', 'LIKE', '%,' . $cate . ',%')->where('is_recommend', 0)->limit($diff)->select()->append(['course_type_text', 'user_price']);
            $course  = array_merge($course->toArray(), $_course->toArray());
        }
        return $course;
    }

    public function getTitleById($id)
    {
        return $this->where('id', $id)->value('title') ?: '';
    }

    /**
     * @throws DbException
     */
    public function getTeacherCourseByCourseType($course_type, $teacher_id = 0, $field = '*'): Paginator
    {
        $teacher_id = $teacher_id ?: request()->mid;

        if ($course_type == 4) {
            // 班级课程
            $course = $this->getTeacherClasses($teacher_id, $field);
        } else {
            // 其他课程
            $course = app(Course::class)->where('teacher_id', $teacher_id)->scope('status,listingTime,solo,expire')->field($field)->with(['cover_url'])->where('course_type', $course_type)->append(['course_type_text', 'user_price'])->paginate(static::getListRows());
        }
        return $course;
    }

    /**
     * @throws DbException
     */
    public function getTeacherClasses($teacher_id = 0, $field = '*'): Paginator
    {
        //
        $classes = $this->alias('course')->join('course_classes_link link', 'link.course_id=course.id')
            ->where([
                ['course.course_type', 'IN', '1,2,3'],
                ['course.teacher_id', '=', $teacher_id],
                ['course.status', '=', 1],
                ['course.listing_time', '<', time()],
                ['course.delete_time', '=', null],
            ])->column('classes_id');

        return $this->field($field)->where('id', 'IN', $classes)->scope('status,listingTime')->with(['cover_url'])->append(['course_type_text', 'user_price'])->paginate(static::getListRows());
    }

    /**
     * 获取课程讲师用户ID
     */
    public function getCourseTeacherUid($courseId)
    {
        $teacher_id = $this->whereId($courseId)->value('teacher_id');
        return app(Teacher::class)->getTeacherUserId($teacher_id);
    }

    /**
     * 查询课时解锁状态
     * Author: 亓官雨树 <lucky.max@foxmail.com>
     * Date: 22/12/27
     *
     * @param $param
     * @return bool
     */
    public function isCourseSecUnlock($param): bool
    {
        // 数据验证
        $this->getValidate()->scene('unlock')->failException()->check($param);
        // 参数
        $courseId  = $param['course_id'];
        $sectionId = $param['section_id'];
        $classId   = intval($param['class_id'] ?? 0);
        /** @var CourseSection $secLogic */
        $secLogic = app(CourseSection::class);
        return $secLogic->isPrevSecLearnFinished($courseId, $sectionId, $classId);
    }

    /**
     * 课程获取弹题
     * Author   亓官雨树<lucky.max@foxmail.com>
     * Date 2023-06-26
     */
    public function getCourseQuestion($id, $class_id): array
    {
        // 判断是否为班级课下的课程
        $courseIds = app(ClassesLink::class)->getCourseIds($class_id);
        $newId     = in_array($id, $courseIds) ? $class_id : $id;

        $data = $this->where('id', $newId)->field('mhm_id,onhook_type')->findOrEmpty();
        if ($data->isEmpty()) throw new LogicException('课程不存在');
        if ($data->onhook_type != 2) throw new LogicException('防挂机配置不为弹题验证');

        // 弹题
        $question = $this->getQuestion(request()->mhm_id);
        // 获取随机试题
        $popup = app(Popup::class)->get('video', intval($id));
        $bids  = array_values(array_unique(array_column($popup, 'value'))) ?? 0;
        $qids  = app(TopicLogic::class)->where('question_bank_id', 'IN', $bids)->where('question_type', '<', 3)->orderRaw('rand()')->limit(1)->field('id')->findOrEmpty();
        if ($qids) {
            $tid               = (string)$qids->id;
            $topic             = app(TopicLogic::class)->getTopicInfoById($tid);
            $question['topic'] = $topic;
        }

        return $question;
    }

    /**
     * 课程获取课件列表
     * Author   亓官雨树<lucky.max@foxmail.com>
     * Date 2023-06-26
     */
    public function getCourseWare($id): array
    {
        $mid = request()->mid;

        $data = $this->where('id', $id)->findOrEmpty();
        if ($data->isEmpty()) throw new LogicException('课程不存在');

        return app(Courseware::class)->with(['course', 'attach', 'record' => function ($query) use ($mid, $id) {
            $query->where('course_id', $id)->where('user_id', $mid);
        }])->where('course_id', $id)->order('create_time', 'desc')->select()->each(function ($i) {
            $attach            = app(Attach::class)->findOrEmpty($i->attach_id);
            $i->attach_data    = !$attach->isEmpty() ? [
                'attach_id' => $attach->id,
                'savename'  => $attach->savename,
                'fileurl'   => $attach->fileurl,
                'stock'     => $attach->stock,
                'filesize'  => $attach->filesize,
            ] : null;
            $i->download_count = $i->download_count ?? 0;
        })->toArray();
    }

    /**
     * 添加课件下载记录
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date：   2023-07-06
     *
     * @param $param
     */
    public function saveCourseWareRecord($param)
    {
        // 验证数据
        $this->getValidate()->scene('ware')->failException()->check($param);

        // 参数
        $course_id = $param['course_id'];
        $ware_id   = $param['ware_id'];
        $mid       = request()->mid;
        // 判断
        $course = $this->where('id', $course_id)->findOrEmpty();
        if ($course->isEmpty()) throw new LogicException('课程不存在');

        $have = app(CourseWareRecord::class)->scope('mid')->where('course_id', $course_id)->where('ware_id', $ware_id)->findOrEmpty();
        if ($have->isEmpty()) {
            $data = [
                'user_id'   => $mid,
                'course_id' => $course_id,
                'ware_id'   => $ware_id,
            ];

            $res = app(CourseWareRecord::class)::create($data);
            if ($res->isEmpty()) {
                throw new LogicException('记录失败');
            }
        } else {
            $have->count       = $have->count + 1;
            $have->create_time = time();
            if (!$have->save()) {
                throw new LogicException('记录失败');
            }
        }
    }

    /**
     * 内部方法
     * Author: 亓官雨树 <lucky.max@foxmail.com>
     * Date: 23/11/21
     *
     * @param $mhmId
     * @return array
     */
    protected function getQuestion($mhmId)
    {
        $system = SystemConfig::get('system.onhook', ['answer_max_number' => 0, 'answer_time_interval' => 0], $mhmId, true);
        return [
            'answer_max_number'    => $system['answer_max_number'] ?? 0,
            'answer_time_interval' => $system['answer_time_interval'] ?? 0,
            'answer_pass'          => $system['answer_pass'] ?? 0,
            'answer_max_count'     => $system['answer_max_count'] ?? 0
        ];
    }
}
